// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//Memory		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
#include "noff.h"
#include "table.h"
#include "synch.h"

// change for P2: Bharat
#include "P2Defs.h"
#include "translate.h"

#include "../threads/system.h"

#include "IPTManager.h"
#include "IPTEntry.h"
#include "machine.h"

extern "C" {
    int bzero(char *, int);
};

Table::Table(int s) : map(s), table(0), lock(0), size(s) {
    table = new void *[size];
    lock = new Lock("TableLock");
}

Table::~Table() {
    if (table) {
        delete table;
        table = 0;
    }
    if (lock) {
        delete lock;
        lock = 0;
    }
}

void *Table::Get(int i) {
    // Return the element associated with the given if, or 0 if
    // there is none.

    return (i >= 0 && i < size && map.Test(i)) ? table[i] : 0;
}

int Table::Put(void *f) {
    // Put the element in the table and return the slot it used.  Use a
    // lock so 2 files don't get the same space.
    int i; // to find the next slot

    lock->Acquire();
    i = map.Find();
    lock->Release();
    if (i != -1)
        table[i] = f;
    return i;
}

void *Table::Remove(int i) {
    // Remove the element associated with identifier i from the table,
    // and return it.

    void *f = 0;

    if (i >= 0 && i < size) {
        lock->Acquire();
        if (map.Test(i)) {
            map.Clear(i);
            f = table[i];
            table[i] = 0;
        }
        lock->Release();
    }
    return f;
}

void *Table::RemoveFirst() {
    //similler to the Remove. Just remove the first item.
    void *f = 0;
    int i = 0;

    lock->Acquire();
    for (i = 0; i < size; i++) {
        if (!map.Test(i)) {
            //first used entry found, remove this.
            break;
        }
    }
    lock -> Release();
    if (i < size) {
        return Remove(i);
    }
    return f;
}

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void
SwapHeader(NoffHeader *noffH) {
    noffH->noffMagic = WordToHost(noffH->noffMagic);
    noffH->code.size = WordToHost(noffH->code.size);
    noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
    noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
    noffH->initData.size = WordToHost(noffH->initData.size);
    noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
    noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
    noffH->uninitData.size = WordToHost(noffH->uninitData.size);
    noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
    noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//
// 	Dealloate an address space.  release pages, page tables, files
// 	and file tables
//----------------------------------------------------------------------

AddrSpace::~AddrSpace() {
    delete pageTable;
    delete exeFile;
}

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters() {
    int i;

    for (i = 0; i < NumTotalRegs; i++)
        machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

    // Set the stack register to the end of the address space, where we
    // allocated the stack; but subtract off a bit, to make sure we don't
    // accidentally reference off the end!
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    // DEBUG('A', "AddrSpace: Initializing stack register to 0x%x(%d)\n", numPages * PageSize - 16, numPages * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, nothing!
//----------------------------------------------------------------------

void AddrSpace::SaveState() {
    
    IntStatus ints = interrupt->SetLevel(IntOff);

    // DEBUG('C', "AddrSpace: In SaveState() for PID:%d. TID=%d Updating the TLB Dirtybits.\n", pid, currentThread->getThreadID());

    //cache the TLB Entries.
    for (int i = 0; i < TLBSize; i++) {
        if (machine->tlb[i].valid == TRUE && machine->tlb[i].dirty == TRUE) {
            pageTable[machine->tlb[i].virtualPage].dirty = TRUE;
            iptm -> markAsDirty(pid, machine->tlb[i].virtualPage, TRUE, machine->tlb[i].physicalPage, 1);
        }
    }

    // DEBUG('C', "AddrSpace: END SaveState() for PID:%d. TID=%d Updating the TLB Dirtybits.\n", pid, currentThread->getThreadID());
    interrupt -> SetLevel(ints);
}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() {
    
    IntStatus ints = interrupt->SetLevel(IntOff);
    // DEBUG('C', "\n\nAddrSpace: In RestoreState() for PID:%d. TID=%d. Prev PID=%d.\n", pid, currentThread->getThreadID(), iptm -> previousPID);
    if (iptm -> previousPID != pid) {
        // DEBUG('D', "AddrSpace: Context switch between two processes. Current PID: %d, Previous PID:%d. Invalidating TLB.\n", pid, iptm -> previousPID);

        for (int i = 0; i < TLBSize; i++) {
            machine->tlb[i].valid = FALSE;
        }
    }
    iptm -> previousPID = pid;
    // DEBUG('C', "AddrSpace: End RestoreState() for PID:%d. TID=%d Updating the TLB Dirtybits.\n", pid, currentThread->getThreadID());
    interrupt -> SetLevel(ints);
}

void AddrSpace::printPageTable() {
    //NOTE: DONT USE THE LOCK HERE, OTHREWISE YOU WILL GET A DEADLOCK.
    // THIS METHOD SHOULD BE CALLED FORM THREAD-SAFE METHODS OF THIS CLASS

    if (DebugIsEnabled('W')) {
        printf("\nPageTable:\n\tPages: %d\tpgTableSize: %d\n", numPages, numPages * PageSize);

        for (int i = 0; i < numPages; i++) {
            pageTable[i].printIPTEntry();
            fflush(stdout);
        }
        printf("\n\n");
    }

}

/* Ensure that there are numToEnsure pages in the page table will grow the table if need be. */
int AddrSpace::EnsurePageTable(int numToEnsure) {

    // addrLock -> Acquire() ;

    // DEBUG('D', "AddrSpace: EnsurePageTable start.\n");

    // DEBUG('A', "AddrSpace: EnsurePageTable. NumToEnsure: %d, oldTableSize: %d\n", numToEnsure, numPages);

    if (numPages >= numToEnsure) {
        // we already have these many pages... No need to do proceed
        // DEBUG('A', "AddrSpace: PageTable Size is alreay big enough. No need to expand it.\n");
        // DEBUG('D', "AddrSpace: EnsurePageTable END. Size is alreay big enough.\n");

       // addrLock -> Release();

        return numPages;

    } else {
        //Grow the page table:
        IPTEntry *oldTable;
        IPTEntry *newTable = new IPTEntry[numToEnsure];
        memcpy(newTable, pageTable, sizeof (IPTEntry) * numPages);
        //Grow the table, but until something is allocate

        for (int i = numPages; i < numToEnsure; i++) {
            newTable[i].valid = FALSE;
            newTable[i].virtualPage = i;
            newTable[i].pageLocation = IPTEntry::LOC_UNDEFINED;
            newTable[i].pageType = IPTEntry::TYPE_DATA_PAGE;
            newTable[i].pid = pid;
            newTable[i].readOnly = FALSE;
        }

        oldTable = pageTable;
        pageTable = newTable;

        delete oldTable;
        numPages = numToEnsure;

        // DEBUG('A', "AddrSpace: End of EnsurePageTable. NumToEnsure: %d, NewTableSize: %d\n", numToEnsure, numPages);
        // DEBUG('D', "AddrSpace: EnsurePageTable END.\n");

        // addrLock -> Release() ;
        return numPages;
    }
}

int AddrSpace::CreateNewStack(int tid) {
    //We need to copy the pages from the parent's stack into a newly allocated set for the child.
    int i = 0;

    addrLock -> Acquire();
    // DEBUG('D', "AddrSpace: CreateNewStack start. TID:%d\n", tid);

    // DEBUG('A', "AddrSpace: Inside creating a new stack. TID: %d\n", tid);

    int numStackPages = divRoundUp(UserStackSize, PageSize);

    // DEBUG('5', "AddrSpace: Allocating %d pages for %d bytes\n", numStackPages, UserStackSize);
    // We always want to add onto the end of this space regardless of the space it has.

    unsigned int nPgTableSize = stackStartVpn + (tid + 1) * numStackPages;
    // DEBUG('5', "AddrSpace: New PageTable Size now is: %d\n", nPgTableSize);

    EnsurePageTable(nPgTableSize);

    // printPageTable();
    addrLock -> Release();
    // DEBUG('D', "AddrSpace: CreateNewStack END. TID:%d\n", tid);
    return 1;
}

int AddrSpace::DestroyStack(int tid) {

    int i = 0;
    int numStackPages = divRoundUp(UserStackSize, PageSize);
    // get the page-table entry for this thread

    int startEntry = stackStartVpn + (tid * numStackPages);

    addrLock -> Acquire();
    // DEBUG('D', "AddrSpace: DestroyStack start. TID:%d\n", tid);


    // DEBUG('6', "AddrSpace: Inside destroy stack. TID: %d\n", tid);
    if (startEntry >= numPages) {
        // DEBUG('6', "AddrSpace: #### Error: Start entry(%d) for TID:%d is greater than pageSize%d\n", startEntry, tid, numPages);
        // DEBUG('D', "AddrSpace: DestroyStack end. TID:%d\n", tid);
        addrLock -> Release();
        return -1;
    }

    //free all user-stack pages.
    for (i = 0; i < numStackPages; i++) {
        if (pageTable[startEntry + i].valid == TRUE) {
            pageTable[startEntry + i].valid = FALSE;

            // if page is in swap / IPT clear it...
            if (pageTable[startEntry + i].pageLocation == IPTEntry::LOC_SWAP_FILE) {
                // DEBUG('E', "AddrSpace: DestroyStack . Entry %d is in SWAP at SwapLoc=%d, TID:%d. Meaking as void\n", (startEntry + i), pageTable[startEntry + i].swapOffset, tid);
                sfm -> markPageAsVoid(pageTable[startEntry + i].swapOffset);
            } else if (pageTable[startEntry + i].pageLocation == IPTEntry::LOC_MEMORY) {

                IntStatus ints = interrupt -> SetLevel(IntOff);
                //if in TLB make tahat entry invalid.
                for (int j = 0; j < TLBSize; j++) {
                    if (machine->tlb[j].virtualPage == pageTable[startEntry + i].virtualPage && machine->tlb[j].valid == TRUE) {
                        machine->tlb[j].valid = FALSE;
                        break;
                    }
                }
                interrupt -> SetLevel(ints);
                iptm -> freePhysPage(pid, pageTable[startEntry + i].virtualPage , pageTable[startEntry + i].physicalPage);
            }

            pageTable[startEntry + i].pageLocation = IPTEntry::LOC_UNDEFINED;

            // DEBUG('6', "AddrSpace: Freeying page VPN:%d, PPN=%d for TID: %d\n", pageTable[startEntry + i].virtualPage, pageTable[startEntry + i].physicalPage, tid);
        }
    }
    addrLock -> Release();
    // DEBUG('6', "AddrSpace: End of destroy stack. TID: %d\n", tid);

    return 1;
}

AddrSpace::AddrSpace(OpenFile* exe) : fileTable(MaxOpenFiles) {
    printf("AddrSpace: The AddrSpave(OpenFile* executable) constructor is not used anymore. If you see this than there's something wrong.\n");
}

AddrSpace::AddrSpace() : fileTable(MaxOpenFiles) {

    // Don't allocate the input or output to disk files
    fileTable.Put(0);
    fileTable.Put(0);
    addrLock = new Lock("AddrSpaceLock");
    //mutex = new Semaphore("AddrSema" , 1 );
    clockIndex = 0;
    tlbCache = new TranslationEntry[TLBSize];
}

int AddrSpace::LoadForVM(OpenFile *executable) {
    unsigned int size = 0;
    int i = 0;
    NoffHeader noffH;

    int codePages;

    addrLock -> Acquire();
   // mutex -> P() ;

    // DEBUG('D', "AddrSpace: LoadForVM start. PID=%d\n", pid);

    exeFile = executable;

    //Get the NOFF header
    executable->ReadAt((char *) & noffH, sizeof (noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
        SwapHeader(&noffH);

    if (noffH.noffMagic != NOFFMAGIC) {
        DEBUG('A', "AddrSpace: The executable could not be loaded. (Bad file header)\n");
        printf("AddrSpace: The executable could not be loaded. (Bad file header)\n");
        //mutex -> V() ;
        addrLock -> Release();
        return -1;
    }

    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size;

    numPages = divRoundUp(size, PageSize) +  divRoundUp(UserStackSize, PageSize) ;
    stackStartVpn = numPages - divRoundUp(UserStackSize, PageSize);

    codePages = noffH.code.size / PageSize;

    // DEBUG('2', "AddrSpace: Initializing address space PID=%d, num pages %d, size %d, CodePages: %d, UserStackStart=%d\n", pid, numPages, size, codePages, stackStartVpn);

    pageTable = new IPTEntry[numPages];

    for (i = 0; i < codePages; i++) {
        pageTable[i].virtualPage = i;
        pageTable[i].pageType = IPTEntry::TYPE_CODE_PAGE;
        pageTable[i].pid = pid;
        pageTable[i].pageLocation = IPTEntry::LOC_EXE_FILE;
        pageTable[i].swapOffset = i;
        pageTable[i].readOnly = TRUE;
    }

    for (i = codePages; i < numPages; i++) {
        pageTable[i].virtualPage = i;
        pageTable[i].pageType = IPTEntry::TYPE_DATA_PAGE;
        pageTable[i].pid = pid;
        pageTable[i].pageLocation = IPTEntry::LOC_UNDEFINED;
        pageTable[i].readOnly = FALSE;

    }
    //mutex -> V();
    addrLock -> Release();
    // DEBUG('D', "AddrSpace: LoadForVM end. PID=%d\n", pid);

    return 1;
}

int AddrSpace::loadPageInMemory(int ppn, int vmAddress) {

    int ret = 1;
    //mutex -> P() ;
    //addrLock -> Acquire();

    // DEBUG('D', "AddrSpace: loadPageInMemory start. PID=%d\n", pid);
    int vpn = vmAddress / PageSize;

    if (numPages <= vpn || vmAddress < 0) {
        //DEBUG('A', "AddeSpace:Cannot load page in memory. Vaddr 0x%x(%d) converts to VPN: %d, but total VPN is %d\n", vmAddress, vmAddress, vpn, numPages);
        ret = -1;
    } else {
        //This is a valid Virtual Address.. determine where the page is

        if (pageTable[vpn].valid == FALSE) {
            bool shouldCopyFromExe = false;

            // This is the first ever reference for the pagetable entry. Page is either code page(in exe) or data page(not loaded yet in memory)
            // DEBUG('A', "AddeSpace:Page %d(PID:%d) is referenced for the first time \n", vpn, pid);

            if (pageTable[vpn].pageType == IPTEntry::TYPE_DATA_PAGE) {
                //page is either init/uninit or stack page that never got into memory
                //if this is an init/in-init data page copy it form exefile.
                if (vpn < stackStartVpn) {
                    // DEBUG('A', "AddeSpace:Page %d(PID:%d) is init / un-init page that was never refenced. Copying it form ExeFile.\n", vpn, pid);
                    shouldCopyFromExe = TRUE;
                } else {
                    // DEBUG('A', "AddeSpace:Page %d(PID:%d) is statck page that was never refenced. No need do anything.\n", vpn, pid);
                    shouldCopyFromExe = FALSE;
                }
            } else if (pageTable[vpn].pageType == IPTEntry::TYPE_CODE_PAGE) {
                // DEBUG('A', "AddeSpace:Page %d(PID:%d) is a code page that was never refernced before.Copying from EXE file.\n", vpn, pid);
                shouldCopyFromExe = TRUE;
            } else {
                // DEBUG('W', "AddeSpace: ?????? Page %d(PID:%d) is INVALID but location is neither EXE or UNDEF. Wrong...\n", vpn, pid);
            }
            if (shouldCopyFromExe == TRUE) {
                if (exeFile == NULL) {
                    // DEBUG('A', "AddeSpace: Trying to get page VPN=%d (PID=%d) form EXE file, but the EXEFILE is NULL...\n", vpn, pid);
                    //mutex -> V() ;
                    //addrLock -> Release();
                    return -1;
                }

                NoffHeader noffH;
                unsigned int i, size;
                exeFile->ReadAt((char *) & noffH, sizeof (noffH), 0);
                if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
                    SwapHeader(&noffH);
                ASSERT(noffH.noffMagic == NOFFMAGIC);

                // decide where to copy to.
                char* destAddr = (char*) (machine->mainMemory + (ppn * PageSize));

                // decide the file position in file.
                int inFilePos = noffH.code.inFileAddr + (vpn * PageSize);

                // DEBUG('A', "AddeSpace: Reading page VPN=%d (PID=%d). Dest PPN:%d,Addr=%d , form EXE file. InFilePos = %d .\n", vpn, pid, ppn, (int) destAddr, inFilePos);
                exeFile -> ReadAt(destAddr, PageSize, inFilePos);

                ret = 1;
            }

            // we are loading it for the first time..hence not dirty pages.
            pageTable[vpn].dirty = FALSE;

        } else {
            // this is a valid pageTable entry. And has been refernecd in past.
            // DEBUG('A', "AddeSpace:Page %d(PID:%d) is a valid entry.\n", vpn, pid);
            if (pageTable[vpn].pageLocation == IPTEntry::LOC_SWAP_FILE) {

                // DEBUG('A', "AddeSpace:Page %d(PID:%d) is in swap file. Swap Loc:%d\n", vpn, pid, pageTable[vpn].swapOffset);
                ret = sfm -> readPageFromSwapFile(ppn, pageTable[vpn].swapOffset);

                // since we replace the entries in swap file, mark this page as dirty.
                pageTable[vpn].dirty = TRUE;

                pageTable[vpn].swapOffset = -1;

            } else if (pageTable[vpn].pageLocation == IPTEntry::LOC_EXE_FILE) {

                if (exeFile == NULL) {
                    // DEBUG('A', "AddeSpace: Trying to get VALID page VPN=%d (PID=%d) form EXE file, but the EXEFILE is NULL...\n", vpn, pid);
                    //addrLock -> Release();
                    // mutex -> V() ;
                    return -1;
                }

                if (pageTable[vpn].pageType != IPTEntry::TYPE_CODE_PAGE) {
                    DEBUG('W', "??? AddeSpace: Entry VPN = %d (PID=%d) is a valid CODE page entry but NOT of EXE page...\n", vpn, pid);
                }

                NoffHeader noffH;

                exeFile->ReadAt((char *) & noffH, sizeof (noffH), 0);

                if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
                    SwapHeader(&noffH);
                ASSERT(noffH.noffMagic == NOFFMAGIC);

                // decide where to copy to.
                char* destAddr = (char*) (machine->mainMemory + (ppn * PageSize));


                // decide the file position in file.
                int inFilePos = noffH.code.inFileAddr + (vpn * PageSize);

                // DEBUG('A', "AddeSpace: Reading VALID page VPN=%d (PID=%d). Dest Addr=%d , form EXE file. InFilePos = %d .\n", vpn, pid, (int) destAddr, inFilePos);
                exeFile -> ReadAt(destAddr, PageSize, inFilePos);

                // since we have a bacjup copy in EXE we say it's not a dirty page.
                pageTable[vpn].dirty = FALSE;

                ret = 1;
            } else {
                DEBUG('W', "??? AddeSpace: Entry VPN = %d (PID=%d) is a valid entry but couldn't find in SWAP or EXE...\n", vpn, pid);
            }
        }
    }

    //mark the pageTable entry as valid.
    pageTable[vpn].valid = TRUE;
    // mark the location as in memory.
    pageTable[vpn].pageLocation = IPTEntry::LOC_MEMORY;
    pageTable[vpn].physicalPage = ppn;

    //set the age for FIFO
    pageTable[vpn].age = iptm->getNextAge();

    // DEBUG('D', "AddrSpace: loadPageInMemory end. PID=%d\n", pid);

    // addrLock -> Release();
    return ret;
}

void AddrSpace::LoadAddress(int vmAddress) {

    addrLock -> Acquire();
    
    int vpn = vmAddress / PageSize;
    /* Check for valid Virtual address . */
    if (numPages <= vpn || vmAddress < 0) {
        DEBUG('A', "AddeSpace: $$$ Error: Invalid Virtual Address. Vaddr 0x%x(%d) converts to VPN: %d, but total VPN is %d\n",
                vmAddress, vmAddress, vpn, numPages);
        printf("AddeSpace: $$$ Error: Invalid Virtual Address. Vaddr 0x%x(%d) converts to VPN: %d, but total VPN is %d\n",
                vmAddress, vmAddress, vpn, numPages);

        addrLock -> Release();
        //interrupt -> SetLevel(intsd) ;

        onOtherException();
        interrupt -> Halt();
        return;

    } else {
        
        // DEBUG('D', "AddrSpace: LoadAddress start. PID=%d, VPN=%d\n", pid, vpn);

        // DEBUG('A', "AddrSpace: PageFault for PID:%d and VPN:%u.\n", pid, vpn);
        //serach in IPT first.
        pfLock -> Acquire() ;
    
        // IntStatus intsd = interrupt-> SetLevel(IntOff) ;
        
        IPTEntry* ent = iptm -> SearchIPT(pid, vpn);
       
        if (ent == NULL) {
            
           // DEBUG('A', "AddrSpace: IPT miss for PID:%d and VPN:%u.\n", pid, vpn);

            //get a new page for IPTManager
            int newPPN = iptm -> getPhysPage(pid);
            
            //load page into memory
            loadPageInMemory(newPPN, vmAddress);
            
            
            iptm -> addIPTEntry(& pageTable[vpn]) ;
            
            TLBSwap(clockIndex, & (pageTable[vpn]));
            clockIndex = (clockIndex + 1) % TLBSize;

        
        } else if (ent != NULL) {
            // DEBUG('A', "AddrSpace: LoadAddress vmAddress:%d VPN:%d physPage:%d\n", vmAddress, vpn, ent->physicalPage);
            TLBSwap(clockIndex, ent);
            // DEBUG('A', "AddrSpace: IPT hit, Next TLB index to update:%d\n", clockIndex);
            clockIndex = (clockIndex + 1) % TLBSize;
        }
        
        pfLock -> Release() ;
       // interrupt->SetLevel(intsd);
    }     
    addrLock -> Release();
    // DEBUG('D', "AddrSpace: LoadAddress end. PID=%d, VPN=%d\n", pid, vpn);
}

void AddrSpace::TLBSwap(int oldPage, IPTEntry *newEntry) {

    //disable interrupts.
    IntStatus ints = interrupt->SetLevel(IntOff);

    if (!newEntry) {
        DEBUG('R', "AddrSpace: Error in TLB Swap. Null entry passed. Skipping the replacement.\n");
        interrupt -> SetLevel(ints);
        return;
    }

    // DEBUG('R', "AddrSpace: TLB Swap. Swapping %d now. New VPN=%d, PPN=%d \n", oldPage, newEntry ->virtualPage, newEntry ->physicalPage);

    // DEBUG('D', "AddrSpace: TLBSwap start. PID=%d\n", pid);

    // if TLB has updated this entry than update this in PageTable and IPT as well.
    if (machine->tlb[oldPage].valid == TRUE && machine->tlb[oldPage].dirty == TRUE) {
        int replaceVpn = machine->tlb[oldPage].virtualPage;
        iptm -> markAsDirty(pid, replaceVpn, machine->tlb[oldPage].dirty, machine->tlb[oldPage].physicalPage , 2);
        pageTable[machine->tlb[oldPage].virtualPage].dirty = TRUE;
    }

    machine->tlb[oldPage].physicalPage = newEntry ->physicalPage;
    machine->tlb[oldPage].virtualPage = newEntry ->virtualPage;
    machine->tlb[oldPage].dirty = newEntry ->dirty;
    machine->tlb[oldPage].valid = TRUE;
    machine->tlb[oldPage].readOnly = newEntry ->readOnly;
    machine->tlb[oldPage].use = newEntry ->use;

    // DEBUG('D', "AddrSpace: TLBSwap end. PID=%d\n", pid);
    //restore interrupts
    interrupt->SetLevel(ints);
}

int AddrSpace::markPageAsEvicted(int vpn, int swapLoc, int ppn, bool isDirty_) {
    int ret = -1;

    // addrLock -> Acquire();

    //printf( "AddrSpace: markPageAsEvicted start. PID=%d, SwapOffset: %d\n", pid, swapLoc);
    // DEBUG('E', "AddrSpace: markPageAsEvicted start. PID=%d, VPN=%d, SwapLoc=%d, PPN=%d, ISDirty=%d\n", pid, vpn, swapLoc, ppn, isDirty_);

    if (vpn < numPages) {
        // vpn is valid. Do the marking.
        if (pageTable[vpn]. physicalPage != ppn) {
            DEBUG('W', "AddrSpace: ???? This is strange. Marking for eviction, but PPN Dont match ???. ");
        }
        if (pageTable[vpn].valid == FALSE) {
            DEBUG('W', "AddrSpace: ???? This is strange. Marking for eviction, Entry is not valid. VPN=%d, PID=%d ", vpn, pid);
        }

        if (pageTable[vpn].pageType == IPTEntry::TYPE_CODE_PAGE) {
            // DEBUG('E', "VPN=%d, PID=%d is CODE page. Change loc = EXE\n", vpn, pid);
            pageTable[vpn].physicalPage = -1;
            pageTable[vpn].pageLocation = IPTEntry::LOC_EXE_FILE;
        } else if (pageTable[vpn].pageType == IPTEntry::TYPE_DATA_PAGE) {

            if (isDirty_ == TRUE) {
                // DEBUG('E', "VPN=%d, PID=%d is dirty Data page. Change loc = SWAP. Offset=%d\n", vpn, pid, swapLoc);
                pageTable[vpn].pageLocation = IPTEntry::LOC_SWAP_FILE;
                
                pageTable[vpn].swapOffset = swapLoc;
            } else {
                // DEBUG('E', "VPN=%d, PID=%d is non - dirty Data page. Change loc = Undef. valid= false\n", vpn, pid);
                pageTable[vpn].pageLocation = IPTEntry::LOC_UNDEFINED;
                pageTable[vpn].valid = FALSE ;
            }
            pageTable[vpn].physicalPage = -1;
        }
        else {
            DEBUG('W', "AddrSpace: ???? Marking for eviction, but PageType is neither CODE nor DATA. i.e. Never got into memory... ");
        }

        
        // pageTable[vpn].physicalPage = -1;
        // DEBUG('E', "AddrSpace: Marked page as evicted. VPN=%d, ENTRY-PID=%d, ADDRSPACE-PID=%d , SWAPOFFSET=%d\n",pageTable[vpn].virtualPage, pageTable[vpn].pid, pid, pageTable[vpn].swapOffset);

        ret = 1;
    }

    // pageTable[vpn].printIPTEntry();

    // DEBUG('E', "AddrSpace: markPageAsEvicted end. PID=%d\n", pid);

    // addrLock -> Release();

    return ret;
}

void AddrSpace::onOtherException() {
    printPageTable();
    iptm -> printIPT();

    if (DebugIsEnabled('W')) {
        printf("\nTLB Entries:\n\tPages: %d, TLBSize: %d, Clock: %d\n", TLBSize, TLBSize * PageSize, clockIndex);

        for (int i = 0; i < TLBSize; i++) {
            printf("VPN: %d, PPN: %d, ReadOnly:%s, VALID:%s Dirty:%s\n",
                    machine->tlb[i].virtualPage,
                    machine->tlb[i].physicalPage,
                    (machine->tlb[i].readOnly == TRUE) ? "TRUE" : "FALSE",
                    (machine->tlb[i].valid == TRUE) ? "TRUE" : "FALSE",
                    (machine->tlb[i].dirty == TRUE) ? "TRUE" : "FALSE");
            fflush(stdout);
        }
        printf("\n\n");
    }
}

int AddrSpace::onProcessEnded() {
    int i = 0 ;

    addrLock -> Acquire() ;

    //remove any code/init/uninit data pages.
    
    for (i = 0; i < stackStartVpn ; i++) {
        if (pageTable[i].valid == TRUE) {
            pageTable[i].valid = FALSE;

            // if page is in swap / IPT clear it...
            if (pageTable[i].pageLocation == IPTEntry::LOC_SWAP_FILE) {
                // page is in swap file.
                sfm -> markPageAsVoid(pageTable[i].swapOffset);
            } else if (pageTable[i].pageLocation == IPTEntry::LOC_MEMORY) {

                IntStatus ints = interrupt -> SetLevel(IntOff);
                //if in TLB make tahat entry invalid.
                for (int j = 0; j < TLBSize; j++) {
                    if (machine->tlb[j].virtualPage == pageTable[i].virtualPage
                            && machine->tlb[j].physicalPage == pageTable[i].physicalPage
                            && machine->tlb[j].valid == TRUE) {
                        machine->tlb[j].valid = FALSE;
                        break;
                    }
                }
                interrupt -> SetLevel(ints);
                iptm -> freePhysPage(pid, pageTable[i].virtualPage, pageTable[i].physicalPage);
            }

            pageTable[i].pageLocation = IPTEntry::LOC_UNDEFINED;
        }
    }
    
    addrLock -> Release() ;

    return 1;
}

